iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
4
Modern Web

成為 Modern PHPer系列 第 10

Day 10:PHP 的錯誤處理

  • 分享至 

  • xImage
  •  

錯誤往往會發生,但如何處理才是學問所在。

前言

遇見程式發生預期外的事件,幾乎是每個工程師的日常。PHP 對於錯誤處理往往是混亂而難以控制的,這也時常成為新式語言如 Golang 或 Rust 攻擊的目標。

PHP 有時會丟出 Exception 或 Error (這兩個統稱為 Throwable)

  • new PDO 時設定了錯誤的資訊,這會丟出 PDOException
  • 使用位移運算子(>><<)位移負數位元,這會丟出 ArithmeticError

有時它會用 false、空陣列或 null 表示函式不成功

  • fopen() 失敗時,會丟出一個 PHP Warning 並且回傳 false
  • explode() 如果在第三個參數 limit 使用負數,且第一個參數 delimiter 不存在時,會回傳空陣列
  • json_decode() 如果發生錯誤,回傳 null(在 PHP 7.3 後,可以使用 JSON_THROW_ON_ERROR 丟出 JsonException

Throwable

在 PHP 7.3 所提供的 Throwable 共有以下幾種(已根據繼承關係製成樹狀結構)

  • Error
    • ArithmeticError
      • DivisionByZeroError
    • AssertionError
    • CompileError
      • ParseError
    • TypeError
      • ArgumentCountError
  • Exception
    • ClosedGeneratorException
    • DOMException
    • ErrorException
    • IntlException
    • JsonException
    • LogicException
      • BadFunctionCallException
        • BadMethodCallException
      • DomainException
      • InvalidArgumentException
      • LengthException
      • OutOfRangeException
    • PharException
    • ReflectionException
    • RuntimeException
      • OutOfBoundsException
      • OverflowException
      • PDOException
      • RangeException
      • UnderflowException
      • UnexpectedValueException
    • SodiumException

Error

Error 是 PHP 7 後對於錯誤處理的重大變革,它讓語言層級的 Error 可以被捕獲並處理。

一般而言,開發者並不需要自己製作 Error class,唯一需要的就是捕獲它們並且處理(例如用 Whoops 顯示一個對除錯友善的畫面)

Exception

Exception 算是很廣泛應用的程式語言設計,它們可以讓應用程式友善地處理例外狀況,並且開發者能夠依照自己的需求製作需要的 Exceptions

通常 Exception 分成兩種類型:RuntimeExceptionLogicException。通常我會識別 RuntimeException 是「允許在服務時出現的例外」;LogicException 是「在開發階段就應該解決的例外」

function handleAuthService(string $service)
{
    switch ($service) {
        case 'facebook':
            return new FacebookAuth();
        case 'google':
            return new GoogleAuth();
        default:
            // 使用者可能要求由 GitHub 等其它的服務登入,但系統尚未實作
            // 因為不可能控制使用者的要求,所以這邊丟出 RuntimeException
            throw new RuntimeException('This service is not supported.');
    }
}

try {
    handleAuthService($_GET['type']);
} catch (RuntimeException $e) {
    abort(404, $e->getMessage());
}

錯誤處理技巧

對於內建錯誤,儘量丟成 Exception

內建錯誤包括但不限於 flasenull 或空陣列。

if (! fopen('file', 'r') {
    throw new Exception('File not found.');
}

可以用函名類別丟出 Exception

通常不建議這麼做

try {
    throw new class('Hello Exception') extends Exception {};
} catch (Exception $e) {
    var_dump($e->getMessage());
}

正確使用 Exception

該使用 RuntimeException 時就用,該使用 LogicException 時就用。

盡量在丟的時候不要只丟 Exception,這樣有助於理解並處理它們。

如果在 catch 區塊另外丟出 Exception,在 new 的時候必須附上第三個參數 $previous

try {
    $dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
    throw new DatabaseConnectException(
        'Cannot connet to database.',
        0,
        $e // 這個參數務必記得加,這有助於在找 Exception Stack Trace 時候找到關連
    );
}

後記

儘管在 PHP 7 之後錯誤處理做了大量的改進,但仍然還處於一個混沌的狀態:太多的歷史包袱導致難以變更,PHP 社群的開發能量日益退化與 RFC 投票上的保守思維,這些都導致了 PHP 處於難以前進的阻礙。

註:其實並不是說保守思維不好,畢竟有太多需要考量的東西。目前核心開發組的成員都以不更動 API 為前提進行核心改進(例如 7.4 的 Preload 或 8.0 的 JIT),對於廢棄 API 或變更行為除非存在很大的問題,否則投票通常都不會通過。

註:PHP 核心開發能量日益退化,主因是 Zend API 非常難寫(如果有寫過 PHP Extension 的人應該能理解),當時在 PHP 5.x 的時候有一批開發者退出去做 NodeJS 及 Golang,這也直接導致了 PHP 6 被併入 5.6 的慘案,直到有人主導了 PHP 7(當時稱為 PHP-NG)的開發才穩定下來。


上一篇
Day 09:輸入資料過濾
下一篇
Day 11:使用 composer
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
Lucas Yang
iT邦新手 4 級 ‧ 2020-05-08 10:39:47

少一個括號:

可以用函名類別丟出 Exception
...

try {
    throw new class('Hello Exception') extends Exception {};
} catch (Exception $e) {
    var_dump($e->getMessage();
    // ----------------------^
}
0
moblinfish
iT邦新手 4 級 ‧ 2021-09-28 11:38:30

感謝分享!原本 exception 都亂丟XD

我要留言

立即登入留言